﻿using System;
using System.Collections.Generic;
using System.Linq;
using TinyContainer.Attributes;

namespace TinyContainer
{
    /// <summary>
    /// The default container implementation. 
    /// This one actually does all the dirty work for you.
    /// </summary>
    public class Container : IContainer
    {
        private MappingCollection _mapping;

        /// <summary>
        /// The default contstructor.
        /// </summary>
        public Container()
        {
            this._mapping = new MappingCollection();
            this.Register<IContainer>(this);
        }

        /// <summary>
        /// This is the main function that does all the container creation thing
        /// </summary>
        /// <param name="interface"></param>
        /// <returns></returns>
        public object Resolve(Type @interface,string name="")
        {   
            var instance = this.FindInstanceOfType(@interface,name);
            if (instance != null) return instance;

            var type = this.FindType(@interface,name);
            if (type == null) throw new Exception("The type " + @interface.ToString() + " cannot be resolved");

            var obj = ConstructObject(type);
            if (obj == null) throw new Exception("Cannot resolve " + @interface.ToString());
                        
            this._mapping[@interface, name].Instance = obj;
            
            return obj;           
        }

        /// <summary>
        /// The generic version of resolve. You will
        /// mostly be using this
        /// </summary>
        /// <typeparam name="T">The type to resolve</typeparam>
        /// <returns></returns>
        public T Resolve<T>(string name="")
        {
            return (T)Resolve(typeof(T),name);
        }

        /// <summary>
        /// This method tres to construct the object based on the given constructor
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private object ConstructObject(Type type)
        {			
			var constructor=type.GetConstructors().FirstOrDefault();
            if (constructor!=null)
            {				
                var constparams = new List<object>();
                foreach (var param in constructor.GetParameters())
                {
                    constparams.Add(this.Resolve(param.ParameterType));
                }
                var obj = constructor.Invoke(constparams.ToArray());

                //now check for export params and do the imports
                foreach (var prop in type.GetProperties())
                {
                    var attr = prop.GetCustomAttributes(typeof(ImportAttribute), false).FirstOrDefault() as ImportAttribute;
                    if (attr!=null)
                    {
                        var inject = this.Resolve(prop.PropertyType, attr.Name);
                        prop.SetValue(obj, inject, null);
                    }
                }
                return obj;
            }
            else 
            {
                throw new Exception("There are no constructor for type " + type.ToString());
            }
        }

        /// <summary>
        /// Detects the type for the given interface
        /// if it is an interface it will look for 
        /// a class if it is a class it will just return that class name 
        /// </summary>
        /// <param name="interface"></param>
        /// <returns></returns>
        private Type FindType(Type @interface,string name)
        {
            Type type = null;
            if (@interface.IsInterface)
            {                
                return this._mapping[@interface, name].Class;
            }
            else
            {
                type = @interface;
            }
            return type;
        }

        /// <summary>
        /// looks for the instance of a particular type in the instance mappings
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private object FindInstanceOfType(Type type,string name)
        {	
            return (from val in this._mapping where type.IsInstanceOfType(val.Instance) && val.Name == name select val.Instance).FirstOrDefault();
        }
        /// <summary>
        /// Register an interface to a class
        /// </summary>
        /// <param name="interface"></param>
        /// <param name="class"></param>
        public void Register<T,C>(string name="") where C:T
        {
            var @interface = typeof(T);
            var @class = typeof(C);           
         
            var map = this._mapping[@interface, name];
            map.Class = @class;
            map.Instance = null;

        }

        /// <summary>
        /// Check whether the type os registered or not.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public bool IsRegistered<T>(string name="")
        {
            var type = typeof(T);            
            return this._mapping.IsRegistered(type, name);
        }

        /// <summary>
        /// register an instance to a type
        /// </summary>
        /// <param name="interface"></param>
        /// <param name="instance"></param>
        public void Register<T>(T instance,string name="")
        {
            var @interface = typeof(T);            
            this._mapping[@interface, name].Instance = instance;
        }

        /// <summary>
        /// This method will remove an existing instance from the 
        /// internal instance cache
        /// </summary>
        /// <param name="instance"></param>
        public void Remove(object instance)
        {            
            this._mapping.RemoveInstance(instance);
        }
    }
}
